Structured products#
Technical notes can be skipped on a first reading. These notes end when \(\diamondsuit\) appears.
Zero-Coupon Bond#
A zero-coupon bond \(B\) has a trivial payoff of nominal \(N\) at maturity. It has a price, for a constant interest rate \(r\), given by
where \(T\) is the maturity time. For coherence in the notebook, we use the following notation for the payoff
although in this case the payoff does not depend on the price of the underlying \(S\).
Let us instantiate one example.
[17]:
b = afs.ZCBond(maturity='20211027', calendar=calendars["Act360"])
\(\text{Code}\) - math \((\TeX)\) translation
maturity\(\rightarrow T\),calendar\(\rightarrow\) \(\tau(t,T)\),
\(\tau\) represents the day count convention, see Chapter 1 of Brigo and Mercurio, and will depend on the calendar used. For simplicity, we can think of it as \(\tau(t,t')=t'-t\) as a year fraction.
To understand the payoff, we can see that:
[18]:
# Imputs for the `payoff` method
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
prices = 4000 * np.random.random((1,7,2,3))
print(type(prices))
b.payoff(prices=prices, dates_dic=dates_dic, n=0)
<class 'numpy.ndarray'>
[18]:
array([[[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[ 0., 0.],
[100., 100.]]])
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
We analyze in detail the input and output of the payoff method:
\(\bullet\) For the observation dates \(\{t^1_j\}_{j=0}^{J}\) and the valuation dates \(\{t^0_k\}_{k=0}^{K}\), the price associated with the \(i\)-th simulation of the \(l\)-th asset is a numpy.ndarray denoted by
\(\bullet\) dates_dic is a dictionary (or pandas.Series) which assigns an index to each date. The observation dates \(\{t^1_j\}_{j=0}^{J}\) satisfy \(\{t^1_j\}_{j=0}^{J}\subseteq \{\tau_{j'}\}_{j'=0}^{J'}\), where \(\{\tau_{j'}\}_{j'=0}^{J'}\) are the simulation dates. That is to say, we generally simulate the prices for more dates than the ones we need for a particular product. In this sense, dates_dic can be understood as the map: \(\tau_{j'} \mapsto j'\). In
particular, \(t^1_j \mapsto j'\) such that \(t^1_j = \tau_{j'}.\)
\(\bullet\) n is an integer indicating the number of observation dates previous to a fixed observation date. This is needed in the Monte Carlo engine for exotic (path-dependent) options. For a given n we get the value of the underlying for the previous n observation dates and simulate it for the new observation dates. For more details see the method generate_paths_for_pricing in the class DeterministicVolDiffusionMC of mc_engines.py.
The payoff function returns a numpy.ndarray of 3 dimensions and shape \((|\mathcal{I}|,|\mathcal{J}|-n,|\mathcal{K}|)\), where \(|\mathcal{I}|:= I+1\), \(|\mathcal{J}|:= J+1\) and \(|\mathcal{K}|:=K+1\) are the numbers of elements in the indexes \(i\), \(j\) and \(k\). Note that we give the payoff excluding the previous observation dates. The output satisfies
where \(\delta_{i,j}\) denotes the Kronecker delta and \(j_T\) satisfies \(t^1_{j_T} = T\). We have three dimensions since for the \(|\mathcal{L}|:=L+1\) underlyings, we apply \(\phi_\mathcal{L}(S^0,S^1, S^2,\dots, S^L)\) in order to determine the payoff, where \(\phi_\mathcal{L}:\mathbb{R}^{L+1}\to\mathbb{R}.\) This function depends on the contract. In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each asset.
Finally, note that for every product we are assuming a long position, for a short position we would have \(P_T = -f(S)\). \(\diamondsuit\)
We can use the price method get_px to obtain the value of the zero coupon bond. The arguments are the valuation dates and the discount_curve.
[19]:
curve = afs.CRDC(r=0.05)
b.get_px('20201027', discount_curve=curve)
[19]:
2020-10-27 95.056908
dtype: float64
Note that in this simple case we can obtain the same value directly using
\begin{equation}
\text{Nominal}\times e^{-r T},
\end{equation} i.e., without invoking get_px:
[20]:
r = 0.05
nom = 100
delta = pd.to_datetime('20211027')-pd.to_datetime('20201027') # Initial date and maturity.
T = delta.days/360 # Act360 calendar.
print(f'The price is {nom*np.exp(-r*T)}')
del r, T, nom
The price is 95.05690778250553
Forward#
The value of a forward position at maturity depends on the strike price \(\displaystyle K\) and the underlying price \(S_T\) at that time. For a long position, the payoff at maturity is given by
being \(N\) the nominal. If \(N=K\), we recover the standard formula
In addition, if \(K=0\) we have \(P_T=N S_{T}\).
Let us instantiate one object of this kind.
[21]:
forward = afs.Forward(underlying=equity["SX5E"], maturity="20240415", strike=2000,
calendar=calendars["Act365"], nominal=2000)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),strike\(\rightarrow K\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
As explained in the notebook “Code examples - Structured Products.ipynb” the price of the underlaying for several dates can be obtained using
assets["SX5E"].get_value(["20200903","20201015"])
importing the corresponding libraries. We can check the payoff
[22]:
# Random prices
prices = 5500 * np.random.random((1,3,1,1))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2})
print("\n Payoff: \n", forward.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[1147.5008809 ]]
[[2475.69717812]]
[[2903.38949045]]]]
Payoff:
[[[ 0. ]
[ 0. ]
[903.38949045]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section
The payoff function in this case is
where \(f(S)\) is defined above. In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each underlying. \(\diamondsuit\)
We also check that the analytical formula works. We have
where the payoff only depends on the last value of the price at maturity
[23]:
strike = 2000
s_T = prices[0, -1, 0][0]
print(f'The payoff is {s_T - strike}.')
del strike, s_T
The payoff is 903.3894904452859.
Vanilla options#
The value of a vanilla option (call, put) at maturity depends on the strike price \(\displaystyle K\) and the underlying price \(S_T\) at that time. For a long position, the payoffs at maturity are given by
\(N\) being the nominal and \(\mathbb{1}_A\) the indicator function: \(\mathbb{1}_A(x) = 1\) if \(x\in A\) and 0 otherwise.
Technical note#
The payoff can also be written as
and this how it is implemented in the code. This is based on the call-put parity. \(\diamondsuit\)
As before, if \(N=K\), we recover the standard formula:
where \(A_+:=\max(A,0)\) and \(A_-:=\max(-A, 0)\) so that \(A=A_+-A_-\). This obviously gives us the put-call parity:
In addition, if \(K=0\) we have
Let us instantiate one of both:
[24]:
put = afs.Put(underlying=equity["SX5E"], maturity="20240415", strike=2000,
calendar=calendars["Act365"], nominal=2000)
call = afs.Call(underlying=equity["SX5E"], maturity="20240415", strike=2000,
calendar=calendars["Act365"], nominal=2000)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),strike\(\rightarrow K\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[25]:
# Random prices
prices = 4000 * np.random.random((1,3,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Put payoff: \n", put.payoff(prices = prices, dates_dic=dates_dic, n=0))
print('-'*75)
print("Call payoff: \n",call.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[3968.84451251 496.56792107]]
[[ 357.29549457 1017.74518952]]
[[ 753.8481274 3659.82289311]]]]
---------------------------------------------------------------------------
Put payoff:
[[[ 0. ]
[ 0. ]
[1246.1518726]]]
---------------------------------------------------------------------------
Call payoff:
[[[0.]
[0.]
[0.]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section. Here \(f \equiv f^\text{kind}\).
The payoff function in this case is
where \(f^\text{kind}(S)\) is defined above and \(\text{kind}\in\{\text{call}, \text{put}\}\). In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each underlying. \(\diamondsuit\)
We can also use the plot_payoff method:
[26]:
put.plot_payoff(0, 4000)
Again, we can check the payoff using the analytical formula:
[27]:
strike = 2000
s_T = prices[0, -1, 0, 0]
print(f'For the put option the payoff is {max(-(s_T - strike), 0)}.')
print(f'For the call option the payoff is {max(s_T - strike, 0)}.')
del strike, s_T
For the put option the payoff is 1246.1518725992041.
For the call option the payoff is 0.
For vanilla options we have also methods for obtaining Greeks (\(\delta\), \(\gamma\) and \(\mathcal{V}\)). If the underlying is a LognormalAsset, these are the Greeks in Black-Scholes world, which can be found in Table 6.1 of [Privault, Notes on Stochastic Finance]:
[28]:
curve = afs.CRDC(r = 0.05)
print(f"Delta: {put.get_delta(dates = ['20200303'], discount_curve=curve)}.")
print(f"Gamma: {put.get_gamma(dates = ['20200303'], discount_curve=curve)}.")
print(f"Vega: {put.get_vega(dates = ['20200303'], discount_curve=curve)}.")
Delta: 2020-03-03 -170.6793
Name: Price, dtype: float64.
Gamma: 2020-03-03 0.15849
dtype: float64.
Vega: [1020.96820348].
Greeks#
Greek options are a set of measures used to estimate the sensitivity of an option’s price to changes in different market variables, such as the underlying asset’s price, volatility, time to expiration, interest rates, and dividend payments.
During the analysis, the options and parameters that will be used are defined as follows:
[29]:
dates = "20200903"
ticker = "SX5E"
calendar = "Act360"
discount_curve = "USD LIBOR"
The options are defined as:
[30]:
put = afs.Put(underlying=assets[ticker], maturity="20211027", strike=3000,
calendar=calendars[calendar], nominal=100)
call = afs.Call(underlying=equity[ticker], maturity="20211027", strike=3000,
calendar=calendars[calendar], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\)maturity\(\rightarrow T\),strike\(\rightarrow K\),calendar\(\rightarrow\) \(\tau(t,T)\)nominal\(\rightarrow N\).
The Black Scholes formula for the underlying price \(S_t\) at that time \(t\) is given by:
and
where \(q\) is the dividend yield, \(r\) the interest rate and \(\sigma\) the volatility. Besides, \(\omega\) will be 1 when the option is a call and -1 when it is a put.
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates:
where
Note.#
In the code, to calculate \(d_1\) and \(d_2\), the forward price is used, which is defined as follows:
Delta \(\left(\Delta\right)\).#
Delta measures the sensitivity of an option’s price to changes in the underlying asset’s price. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[31]:
print("Delta of the call: \n", call.get_delta(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Delta of the put : \n", put.get_delta(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
Delta of the call:
2020-09-03 61.482408
Name: Price, dtype: float64
---------------------------------------------------------------------------
Delta of the put :
2020-09-03 -34.626492
Name: Price, dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discounr_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Financial note#
It can be used to assess the risk of their options positions and to hedge their exposure to changes in the underlying asset’s price. By taking opposite positions in an option and the underlying asset, investors can offset their exposure to changes in the asset’s price and limit potential losses.
The delta range varies depending on the type of option. In these cases, assuming that \(q=0\) and \(N=1\), \(\Delta_c \in [0,1]\), \(\Delta_p \in [-1,0]\) and \(\Delta_c -\Delta_p=1\). A high delta indicates that the option’s price will move more closely in line with the underlying asset’s price.
\(\textcolor{red}{Warning}\): \(\Delta_p \in [-1,0]\) and \(\Delta_c -\Delta_p=1\) equality hold when \(q=0\).
Gamma \(\left(\Gamma\right)\).#
Gamma measures the rate of change in the delta of an option for a given change in the underlying asset price. In other words, gamma measures the convexity of the option’s price with respect to changes in the underlying asset price. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[32]:
print("Gamma of the call: \n", call.get_gamma(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Gamma of the put : \n", put.get_gamma(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Gamma of the call:
2020-09-03 0.041501
dtype: float64
---------------------------------------------------------------------------
Gamma of the put :
2020-09-03 0.041501
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Financial note#
Options with high \(\Gamma\) values are more sensitive to changes in the underlying asset’s price and are considered more risky. \(\Gamma\) can be used to manage the risk by adjusting their positions in response to changes in the underlying asset’s price.
\(\Gamma\) is always positive. Options that are far out of the money and have longer expiration dates tend to have lower \(\Gamma\) and,conversely, options that are close to the current price of the underlying asset and have nearer expiration dates tend to have higher \(\Gamma\).
Vega \(\left(\mathcal{V}\right)\).#
Vega measures the rate of change in the option’s price for a given change in the volatility of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[33]:
print("Vega of the call: \n", call.get_vega(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Vega of the put : \n", put.get_vega(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Vega of the call:
[1282.07832785]
---------------------------------------------------------------------------
Vega of the put :
[1282.07832785]
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Theta \(\left(\theta\right)\).#
Theta measures the sensitivity of the option’s price to changes in the time to expiration. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\) and \(\tau = T-t\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[34]:
print("Theta of the call: \n", call.get_theta(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Theta of the put : \n", put.get_theta(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Theta of the call:
2020-09-03 -68.115471
dtype: float64
---------------------------------------------------------------------------
Theta of the put :
2020-09-03 -169.919262
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Rho \(\left(\rho\right)\).#
Rho measures the rate of change in the option’s price for a given change in the risk-free interest rate. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[40]:
print("Rho of the call: \n", call.get_rho(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
Rho of the call:
[1873.81727432]
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Omega \(\left(\Omega\right)\)/ Lambda \(\left(\lambda\right)\).#
Omega measures the sensitivity of the option’s price to changes in the price of the underlying asset. Omega is also known as the elasticity of the option price with respect to changes in the price of the underlying asset. The formula in \(t\) is defined as
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[41]:
print("Omega of the call: \n", call.get_omega(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Omega of the put : \n", put.get_omega(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Omega of the call:
2020-09-03 150.427289
dtype: float64
---------------------------------------------------------------------------
Omega of the put :
2020-09-03 266.055037
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Vanna.#
Vanna measures the rate of change in the \(\Delta\) of the option with respect to changes in the volatility of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[42]:
print("Vanna of the call: \n", call.get_vanna(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Vanna of the put : \n", put.get_vanna(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Vanna of the call:
2020-09-03 -0.141163
dtype: float64
---------------------------------------------------------------------------
Vanna of the put :
2020-09-03 -0.141163
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Charm.#
Charm measures the rate of change in the Delta of the option with respect to changes in the time to expiration of the option. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[43]:
print("Charm of the call: \n", call.get_charm(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Charm of the put : \n", put.get_charm(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Charm of the call:
2020-09-03 0.079497
dtype: float64
---------------------------------------------------------------------------
Charm of the put :
2020-09-03 0.046724
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Vomma/Volga.#
Vomma measures the rate of change in the Vega of the option with respect to changes in the volatility of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[44]:
print("Vomma of the call: \n", call.get_vomma(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Vomma of the put : \n", put.get_vomma(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Vomma of the call:
2020-09-03 179.996359
dtype: float64
---------------------------------------------------------------------------
Vomma of the put :
2020-09-03 179.996359
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Veta.#
Veta measures the rate of change in the price of the option with respect to changes in the time to expiration and the volatility of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[45]:
print("Veta of the call: \n", call.get_veta(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Veta of the put : \n", put.get_veta(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Veta of the call:
2020-09-03 581.688399
dtype: float64
---------------------------------------------------------------------------
Veta of the put :
2020-09-03 581.688399
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Speed.#
Speed measures the sensitivity of the option’s Delta to changes in the price of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[46]:
print("Speed of the call: \n", call.get_speed(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Speed of the put : \n", put.get_speed(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Speed of the call:
2020-09-03 -0.00003
dtype: float64
---------------------------------------------------------------------------
Speed of the put :
2020-09-03 -0.00003
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Zomma.#
Zomma measures the sensitivity of the option’s Gamma to changes in the volatility of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[47]:
print("Zomma of the call: \n", call.get_zomma(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Zomma of the put : \n", put.get_zomma(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Zomma of the call:
2020-09-03 -0.164879
dtype: float64
---------------------------------------------------------------------------
Zomma of the put :
2020-09-03 -0.164879
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Color.#
Color measures the sensitivity of the option’s Gamma to changes in the time to expiration of the option. TLet’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[48]:
print("Color of the call: \n", call.get_color(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Color of the put : \n", put.get_color(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Color of the call:
2020-09-03 -0.000182
dtype: float64
---------------------------------------------------------------------------
Color of the put :
2020-09-03 -0.000182
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Ultima.#
Ultima measures the rate of change of the Vega of an option with respect to changes in the time to expiration of the option.Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[49]:
print("Ultima of the call: \n", call.get_ultima(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Ultima of the put : \n", put.get_ultima(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Ultima of the call:
2020-09-03 -3688.064852
dtype: float64
---------------------------------------------------------------------------
Ultima of the put :
2020-09-03 -3688.064852
dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Dual delta.#
Dual-\(\Delta\) measures the rate of change in the option’s price for a given change in the strike of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[50]:
print("Dual_delta of the call: \n", call.get_dual_delta(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Dual_delta of the put : \n", put.get_dual_delta(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Dual_delta of the call:
[-0.53665411]
---------------------------------------------------------------------------
Dual_delta of the put :
[0.46082681]
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Dual gamma.#
Dual-\(\Gamma\) measures the convexity of the option’s price for a given change in the strike of the underlying asset. Let’s define the underlying price \(S_t\) at that time, the formula is given by
where \(\pi(t,S_t)=\pi_t(S_t)\).
More precisely, if \(\{t^0_k\}_{k=0}^K\) are the dates, it returns:
Let us instantiate one:
[51]:
print("Dual_gamma of the call: \n", call.get_dual_gamma(dates=dates, discount_curve=discount_curves["USD LIBOR"]))
print('-'*75)
print("Dual_gamma of the put : \n", put.get_dual_gamma(dates=dates, discount_curve=discount_curves["USD LIBOR"]) )
Dual_gamma of the call:
2020-09-03 0.000503
Name: SX5E Volatility, dtype: float64
---------------------------------------------------------------------------
Dual_gamma of the put :
2020-09-03 0.000503
Name: SX5E Volatility, dtype: float64
\(\text{Code}\) - math \((\TeX)\) translation
dates\(\rightarrow\) \(\left\{t_k^0\right\}_{k=0}^K\) ,discount_curve\(\rightarrow\) \(\left\{r\left(t_k^0\right)\right\}_{k=0}^K\).
Digital options#
The value of a digital (or binary) option (call, put) at maturity depends on the strike price \(\displaystyle K\) and the underlying price \(S_T\) at that time. For a long position, the payoffs at maturity are given by
\(N\) being the nominal. Note that in some references the definition includes non-strict inequalities for both call and put digital options. Obviously we have:
Sometimes it can be useful to have the strict (resp. non-strict) inequalities in the payoff for the call (resp. put) option, i.e.,
where b stands for “border” (a change in the border with respect to the regular definition). Obviously, in this case we also have
Technical note#
The payoff can also be written as
and this is how it is implemented in the code. \(\diamondsuit\)
Let us instantiate one of both type:
[52]:
digital_put = afs.Digital(underlying=equity["SX5E"], maturity="20240415",
kind='put', strike=2000, calendar=calendars["Act365"],
nominal=2000)
digital_call = afs.Digital(underlying=equity["SX5E"], maturity="20240415",
kind='call', strike=2000, calendar=calendars["Act365"],
nominal=2000)
digital_put_border = afs.Digital(underlying=equity["SX5E"], maturity="20240415",
kind='put', strike=2000,
calendar=calendars["Act365"], nominal=2000,
border=False)
digital_call_border = afs.Digital(underlying=equity["SX5E"], maturity="20240415",
kind='call', strike=2000,
calendar=calendars["Act365"], nominal=2000,
border=False)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),kind\(\rightarrow\) \(\text{kind}\in\{\text{call}, \text{put}\}\),strike\(\rightarrow K\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\),border\(\rightarrow\) True value of border implies \(P_T^\text{call}\) and \(P_T^\text{put}\) (border = Trueby default).
For the payoff, we can check it as follows (without “border” effects):
[53]:
# Random prices
prices = 4000 * np.random.random((1,3,1,1))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Put payoff: \n", digital_put.payoff(prices = prices, dates_dic=dates_dic, n=0))
print('-'*75)
print("Call payoff: \n",digital_call.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[ 340.99730147]]
[[ 354.72012019]]
[[1393.61855441]]]]
---------------------------------------------------------------------------
Put payoff:
[[[ 0.]
[ 0.]
[2000.]]]
---------------------------------------------------------------------------
Call payoff:
[[[0.]
[0.]
[0.]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section. Here \(f \equiv f^\text{kind, b}\).
The payoff function in this case is
where \(f^\text{kind, b}(S)\) is defined above, \(\text{kind}\in\{\text{call}, \text{put}\}\) and \(\text{b}\in\{\text{False}, \text{True}\}\). In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each underlying. \(\diamondsuit\)
We can see now the effects of “border”
[54]:
prices = np.array([10000, 10000, 2000]).reshape(1,3,1,1)
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Call payoff: \n", digital_call.payoff(prices = prices, dates_dic=dates_dic, n=0))
print('-'*75)
print("Call payoff (no border): \n",digital_call_border.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[10000]]
[[10000]]
[[ 2000]]]]
---------------------------------------------------------------------------
Call payoff:
[[[ 0.]
[ 0.]
[2000.]]]
---------------------------------------------------------------------------
Call payoff (no border):
[[[0.]
[0.]
[0.]]]
Airbag options#
The value of an airbag option at maturity depends on strike price \(K_1\) of the put and the strike price \(K_2\) of the call (\(K_1 < K_2\)), and underlying price, \(S_T\) at that time. For a long position, this payoff (at maturity) is given by
with \(N\) being the nominal. Depending on the value of the underlying:
Let us instantiate one object of this kind:
[55]:
airbag = afs.Airbag(underlying=equity["SX5E"], maturity="20280418", low_strike=50, main_strike=100,
calendar=calendars["Act360"], cap_strike=None, nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),low_strike\(\rightarrow K_1\),main_strike\(\rightarrow K_2\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it in two ways, in other words, through the class of the option and through the classes of the options (call, put, and bond) that define the option.
[56]:
# Random prices
prices = 200 * np.random.random((1,7,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
b = afs.ZCBond(maturity="20280418", calendar=calendars["Act360"])
put = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=50, calendar=calendars["Act360"], nominal=50)
call = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100)
airbag2 = b + call - put
print("Pieces payoff", airbag2.payoff(prices, dates_dic, 0))
print("Airbag payoff: \n", airbag.payoff(prices, dates_dic, 0))
Prices:
[[[[137.38030158 180.42717818]]
[[ 49.17453958 164.03283278]]
[[ 10.83387515 124.93699318]]
[[ 66.79606064 39.62576118]]
[[136.65292875 129.61390545]]
[[ 67.56741255 121.87641611]]
[[110.52820892 118.73176666]]]]
---------------------------------------------------------------------------
Pieces payoff [[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[110.52820892]]]
Airbag payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[110.52820892]]]
Since the airbag option is defined with the method new, we must write airbag.payoff(prices, dates_dic, 0)) instead of airbag.payoff(prices=prices, dates_dic=dates_dic, 0))” to perform examples.
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
where \(f(S)\) is defined above.
Since the airbag option is defined with the method __new__, we must write airbag.payoff(prices, dates_dic, 0)) instead of airbag.payoff(prices=prices, dates_dic=dates_dic, 0)) to perform examples.
Financial note#
The airbag method is used ito limit potential losses It involves setting a stop-loss order at a predetermined level below the entry price of a long position or above the entry price of a short position.
[57]:
airbag.plot_payoff(0,150)
[58]:
airbag2.plot_payoff(0,150)
If cap_strike is not None, it’s defined as \(K_3\). So, for a long position, the payoff (at maturity) is given by
Let us instantiate one object of this kind:
[59]:
airbag2 = afs.Airbag(underlying=equity["SX5E"], maturity="20280418", low_strike=50, main_strike=100,
calendar=calendars["Act360"], cap_strike=150, nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),low_strike\(\rightarrow K_1\),main_strike\(\rightarrow K_2\),cap_strike\(\rightarrow K_3\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
[60]:
prices = 200 * np.random.random((1,7,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Airbag payoff: \n", airbag.payoff(prices, dates_dic, 0))
Prices:
[[[[ 84.48861537 46.26801871]]
[[ 36.62868056 70.27710702]]
[[ 26.81688804 125.55960174]]
[[129.23595069 175.30368104]]
[[ 15.00537067 49.07539267]]
[[192.9691331 138.50443569]]
[[171.88485657 138.26151868]]]]
---------------------------------------------------------------------------
Airbag payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[171.88485657]]]
For the payoff, we can check it as follows:
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
[61]:
airbag2.plot_payoff(0,200)
Asian options#
The value of an asian option (call, put) at maturity depends on strike price \(\displaystyle K\) the price of the underlying at \(\{t^1_j\}_{j\in\mathcal{J}}\), the observation dates and the mean function, \(M\). For a long position, the payoffs at maturity are given by
\begin{equation*} P_T^{\text{call, }M}=f^{\text{call, }M}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)=N\times \left(\frac{M\left(S(t^1_0),...,S(t^1_{J})\right)}{K}-1\right)_+\,, \end{equation*}
\begin{equation*} P_T^{\text{put, }M}=f^{\text{put, }M}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)=N\times \left(\frac{M\left(S(t^1_0),...,S(t^1_{J})\right)}{K}-1\right)_-\,, \end{equation*}
\(N\) being the nominal. \(M\) can be two different functions:
Arithmetic Mean: \(M_A(x_1,\ldots,x_n):=\frac1n\sum_{i=1}^n x_i\,\).
Geometric Mean: \(M_G(x_1,\ldots,x_n):=\left(\prod_{i=1}^n x_i\right)^{1/n}\,.\)
Technical note#
In general, for positive numbers (i.e., underlying with positive values), one could define an “averaged” \(l^p\)-norm:
The cases above would correspond to \(p=1\) and \(p\to 0\). By standard inequalities of \(l^p\)-norms:
As a corollary, the price of a (call) geometric Asian option should always be below the price of the arithmetic analogue. \(\diamondsuit\)
Let us instantiate one of both:
[62]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"]
ar_asian = afs.ArAsian(underlying=equity["SX5E"], obsdates=obs_dates, strike=2500, kind='call',
calendar=calendars["Act365"], nominal=2500)
g_asian = afs.GAsian(underlying=equity["SX5E"], obsdates=obs_dates, strike=2500, kind='put',
calendar=calendars["Act365"], nominal=2500)
# If g_asian were a call option we could check that the price is lower than the arithmetic asian
# print("Arithmetic option -> ", ar_asian.get_px(dates=['20200303'], discount_curve=curve))
# print("Geometric option -> ", g_asian.get_px(dates=['20200303'], discount_curve=curve))
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obs_dates\(\rightarrow \{t^1_j\}_{j=0}^{J}\) observation dates. Note that the last observation date corresponds to maturity \(T\),strike\(\rightarrow K\),kind\(\rightarrow\) \(\text{kind}\in\{\text{call}, \text{put}\}\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[63]:
# Random prices
prices = 5000 * np.random.random((1,7,1,1))
print("Prices: \n", prices, "\n")
print("Arithmetic mean:", np.mean(prices))
print("Geometric mean:", np.float(gmean(prices, axis=1)))
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Call arithmetic payoff: \n", ar_asian.payoff(prices = prices, dates_dic=dates_dic, n=0))
print('-'*75)
print("Put geometric payoff: \n", g_asian.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[2055.73008509]]
[[1284.52557489]]
[[4317.47575981]]
[[3436.52441423]]
[[4105.88644123]]
[[3963.38525258]]
[[ 811.37040589]]]]
Arithmetic mean: 2853.5568476721623
Geometric mean: 2441.623259648456
---------------------------------------------------------------------------
Call arithmetic payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[353.55684767]]]
---------------------------------------------------------------------------
Put geometric payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[58.37674035]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function in this case is
where \(f^{\text{kind}, M}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)\) is defined above, \(\text{kind}\in\{\text{call}, \text{put}\}\) and \(M \in \{M_A, M_G\}\). In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each underlying.
In addition, note that if
dates_dic.size > prices.shape[1]
the program returns an IndexError. This occurs because the number of simulation dates (\(|\mathcal{J}'|\)) should be at least equal to the observation dates (in this example dates_dic only contains the observation dates). Specifically, in the code we have:
indices = [dates_dic[date] for date in self.obsdates]
prices = prices[:, indices, :]
\(\diamondsuit\)
Lookback options#
Lookback option with fixed strike#
The value of a fixed lookback option (call, put) at maturity depends on the strike price \(\displaystyle K\) and the price of the underlying at \(\{t^1_j\}_{j\in\mathcal{J}}\), the observation dates. For a long position, the payoffs at maturity are given by
\begin{equation*} P_T^{\text{call}}=f^{\text{call}}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)=N\times \left(\frac{\max\left(S(t^1_0),...,S(t^1_{J})\right)}{K}-1\right)_+\,, \end{equation*}
\begin{equation*} P_T^{\text{put}}=f^{\text{put}}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)=N\times \left(\frac{\min\left(S(t^1_0),...,S(t^1_{J})\right)}{K}-1\right)_-\,, \end{equation*}
\(N\) being the nominal.
Let us instantiate it:
[64]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"]
lookback_fixed_call = afs.Lookback(underlying=equity["SX5E"], obsdates=obs_dates, strike=50, kind='call',
calendar=calendars["Act365"], nominal=100)
lookback_fixed_put = afs.Lookback(underlying=equity["SX5E"], obsdates=obs_dates, strike=50, kind='put',
calendar=calendars["Act365"], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obs_dates\(\rightarrow \{t^1_j\}_{j=0}^{J}\) observation dates. Note that the last observation date corresponds to maturity \(T\),strike\(\rightarrow K\),kind\(\rightarrow\) \(\text{kind}\in\{\text{call}, \text{put}\}\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[65]:
# Random prices
prices = 100 * np.random.random((1,7,1,1))
print("Prices: \n", prices, "\n")
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Lookback call payoff: \n", lookback_fixed_call.payoff(prices = prices, dates_dic=dates_dic, n=0))
print('-'*75)
print("Lookback put payoff: \n", lookback_fixed_put.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[ 1.5473216 ]]
[[27.73549607]]
[[ 7.46885971]]
[[17.24567698]]
[[75.98872368]]
[[48.97694421]]
[[72.47024778]]]]
---------------------------------------------------------------------------
Lookback call payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[51.97744735]]]
---------------------------------------------------------------------------
Lookback put payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[96.9053568]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function in this case is
where \(f^{\text{kind}}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)\) is defined above, \(\text{kind}\in\{\text{call}, \text{put}\}\) In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each underlying.
We can check the payoff using the analytical formula:
[66]:
strike = 50
s_T = prices[0, -1, 0, 0]
print(f'For the put option the payoff is {max(-(np.min(s_T) - strike), 0)}.')
print(f'For the call option the payoff is {max(np.max(s_T) - strike, 0)}.')
del strike, s_T
For the put option the payoff is 0.
For the call option the payoff is 22.470247779739097.
Lookback option with floating strike#
The value of a fixed lookback option (call, put) at maturity depends on the price of the underlying at \(\{t^1_j\}_{j\in\mathcal{J}}\), the observation dates. For a long position, the payoffs at maturity are given by
\begin{equation*} P_T^{\text{call}}=f^{\text{call}}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)=N\times \left(\frac{S_T}{\min\left(S(t^1_0),...,S(t^1_{J})\right)}-1\right)_+\,, \end{equation*}
\begin{equation*} P_T^{\text{put}}=f^{\text{put}}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)=N\times \left(\frac{S_T}{\max\left(S(t^1_0),...,S(t^1_{J})\right)}-1\right)_-\,, \end{equation*}
\(N\) being the nominal.
Let us instantiate it:
[67]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"]
lookback_floating_call = afs.Lookback(underlying=equity["SX5E"], obsdates=obs_dates, strike=None, kind='call',
calendar=calendars["Act365"], nominal=100)
lookback_floating_put = afs.Lookback(underlying=equity["SX5E"], obsdates=obs_dates, strike=None, kind='put',
calendar=calendars["Act365"], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obs_dates\(\rightarrow \{t^1_j\}_{j=0}^{J}\) observation dates. Note that the last observation date corresponds to maturity \(T\),kind\(\rightarrow\) \(\text{kind}\in\{\text{call}, \text{put}\}\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[68]:
# Random prices
prices = 100 * np.random.random((1,7,1,1))
print("Prices: \n", prices, "\n")
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Lookback call payoff: \n", lookback_floating_call.payoff(prices = prices, dates_dic=dates_dic, n=0))
print('-'*75)
print("Lookback put payoff: \n", lookback_floating_put.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[63.59229142]]
[[64.54034165]]
[[19.5164973 ]]
[[47.83699534]]
[[89.1135477 ]]
[[18.17065824]]
[[77.39157983]]]]
---------------------------------------------------------------------------
Lookback call payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[325.91511429]]]
---------------------------------------------------------------------------
Lookback put payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[13.15396836]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function in this case is
where \(f^{\text{kind}}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)\) is defined above, \(\text{kind}\in\{\text{call}, \text{put}\}\). In the code, as it stands, \(\phi_\mathcal{L}:= \min_{l\in\mathcal{L}}\), i.e., the minimum over the set of payoffs for each underlying.
We can check the payoff using the analytical formula:
[69]:
s_T = prices[0, -1, 0, 0]
print(f'For the put option the payoff is {max(-(s_T - np.max(s_T)), 0)}.')
print(f'For the call option the payoff is {max(s_T - np.min(s_T), 0)}.')
del s_T
For the put option the payoff is -0.0.
For the call option the payoff is 0.0.
Barrier options#
The value of a barrier option at maturity depends on the strike price \(\displaystyle K\) and the price of the underlying for the set of observation dates \(\{t^1_j\}_{j=0}^{J}\). For a long position, the payoff at maturity is given by
with \(B\) the barrier, \(\text{a}\in\{\text{up},\text{down}\}\), \(\text{b}\in\{\text{in},\text{out}\}\), \(\text{kind}\in\{\text{put, call}\}\) and
\(\text{ext}^\text{up}=\max\), \(\text{ext}^\text{down}=\min\),
\(R_{\text{up, in}}\text{ equals }\ge\) (so \(R_{\text{up, out}} \text{ equals } <\)) and \(R_{\text{down, in}} \text{ equals } \le\) (so \(R_{\text{down, out}} \text{ equals } >\)),
\(P_T^\text{kind} (S_T)\) was defined above in the Vanilla options section,
\(\text{Ind}\left(A\right)\) is the indicator function of a set \(A\), i.e., \(\text{Ind}(A)=\mathbb{1}_A\).
For instance,
\(P_T^\text{call, up, in} = N\times (S_{T}/K-1)\mathbb{1}_{\{S_T>K\}}\times \text{Ind}\left(\max_{1\le j< T} S(t^1_j)\ge B\right)\,.\)
\(P_T^\text{put, down, out} = N\times (1-S_{T}/K)\mathbb{1}_{\{S_T<K\}}\times \text{Ind}\left(\min_{1\le j< T} S(t^1_j)> B\right)\,.\)
In other words, we have a call or put option which also depends on whether the prices of the underlying at observation dates before maturity surpass (or not) a given barrier.
Let us instantiate one object of this class:
[70]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"
]
call_down_and_out = afs.BarrierOption(underlying=equity["SX5E"], obsdates=obs_dates,
strike=2500, nominal=2500, kind="call",
barrier_kind='down-and-out', barrier=2000,
calendar=calendars["Act365"])
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obs_dates\(\rightarrow \{t^1_j\}_{j=0}^{J}\) observation dates. Note that the last observation date corresponds to maturity \(T\),strike\(\rightarrow K\),nominal\(\rightarrow N\),kind\(\rightarrow \text{kind}\),barrier_kind\(\rightarrow "\text{a}-and-\text{b}"\),barrier\(\rightarrow B\),multiassets_extremum_kind\(\rightarrow \phi_\mathcal{L}\) (default: \(\phi_\mathcal{L} = \max_{l\in\mathcal{L}}\)),calendar\(\rightarrow\) \(\tau(t,T)\),credit_curve\(\rightarrow\) for discounts in Monte Carlo.
The payoff is given by:
[71]:
# Random prices
prices = 4000 * np.random.random((1,7,1,1)) + 1800
print(prices)
# "Deterministic" prices
# prices = np.array([10000, 3000, 3000, 4000, 4000, 4000, 4000, 4000]).reshape(1,8,1)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
call_down_and_out.payoff(prices = prices, dates_dic=dates_dic, n=0)
[[[[3075.72295915]]
[[2308.42352801]]
[[5661.20331425]]
[[4096.79718836]]
[[2671.57008092]]
[[3020.73690227]]
[[2438.98334697]]]]
[71]:
array([[[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.]]])
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section and the “Technical note (payoff method)” in the Asian options section.
The payoff function in this case is
where \(f^\text{kind, a, b}\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)\) is defined above and \(\text{kind}\in\{\text{call}, \text{put}\}\). In the code, as it stands, \(\phi_\mathcal{L}:= \max_{l\in\mathcal{L}}\), i.e., the maximum over the set of payoffs for each underlying. \(\diamondsuit\)
For deterministic prices, we can check the conditions. For instance, if price is not below the barrier after the min_time
[72]:
# "Deterministic" prices
prices = np.array([1000, 2500, 3000, 4000, 4000, 4000, 4000]).reshape(1,7,1,1)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
call_down_and_out.payoff(prices = prices, dates_dic=dates_dic, n=0)
[72]:
array([[[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.]]])
If the price is below the barrier but at the final date, this must not affect the payoff (we consider a put now)
[73]:
# "Deterministic" prices
prices = np.array([3000, 3000, 3000, 4000, 4000, 4000, 1000]).reshape(1,7,1,1)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
put_down_and_out = afs.BarrierOption(underlying=equity["SX5E"], obsdates=obs_dates, strike=2500, nominal=2500,
kind="put", barrier_kind='down-and-out', barrier=2000, calendar=calendars["Act365"])
put_down_and_out.payoff(prices = prices, dates_dic=dates_dic, n=0)
[73]:
array([[[ 0.],
[ 0.],
[ 0.],
[ 0.],
[ 0.],
[ 0.],
[1500.]]])
For multiple assets
[74]:
prices = 4000 * np.random.random([1, 7, 1, 2]) + 1900
print(prices[:])
call_down_and_out = afs.BarrierOption(underlying=equity["SX5E"], obsdates=obs_dates,
strike=2500, nominal=2500, kind="call",
barrier_kind='down-and-out', barrier=2000,
calendar=calendars["Act365"],
multiassets_extremum_kind="min")
call_down_and_out.payoff(prices = prices, dates_dic=dates_dic, n=0)
[[[[5171.99445807 3459.19546242]]
[[5636.64670285 5430.58980261]]
[[2179.71639227 2293.86681499]]
[[5180.35691915 1952.91614716]]
[[3070.51944849 3763.65701173]]
[[5229.38567445 3638.8341207 ]]
[[3362.34057947 2697.16667538]]]]
[74]:
array([[[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.]]])
Knock-out contingent payment#
For one asset
The payoff-vector of this derivative is given by: \begin{equation*}
\begin{aligned}
P_{j}&=&f_j\left(\left(S(t^1_{j'})\right)_{{j'}=0}^{j}\right)=c\times N\times \text{Ind}\left({\max_{j_1\le j'<{j}} S(t^1_{j'})
\(\{t^1_j\}_{j=0}^{j_T}\) are the observation dates,
\(P_j\) is the payment at \(t^1_j\in \{t^1_j\}_{j=0}^{j_T}\),
\(c\) is the coupon rate,
\(N\) the nominal,
\(S(t)\) is the price of the underlying at time \(t\),
\(\text{Ind}\left(A\right)\) is the indicator function of a set \(A\), i.e., \(\text{Ind}(A)=\mathbb{1}_A\),
\(b_{k}\) the knock-out barrier (default value = \(\infty\)),
\(b_{p}\) the payment barrier,
\(t_{j_0}\in \{t^1_j\}_{j=0}^{j_T}\) the time the payment is “activated” (default value = \(t^1_0\)),
\(t_{j_1}\in \{t^1_j\}_{j=0}^{j_T}\) is the first observation date such that the early redemption can be activated (default value = \(t^1_0\)).
That is, the payoff at \(t^1_j\) is zero unless the underlaying didn’t cross the knock-out barrier in the past, the payment wasn’t executed previously, the value of the underlying at \(t^1_j\) is greater than the payment barrier and \(t^1_j\) is greater than the first date the payment can be made.
For the default values this becomes:
For multiple assets
The payoff-vector of this derivative is given by: \begin{equation*}
\begin{aligned}
P_{j}&=&f_{j,\, \mathcal{L}}\left(\left(S(t^1_{j'})\right)_{{j'}=0}^{j}\right)=c\times N\times \text{Ind}\left({\max_{j_1\le j'<{j}} S^b(t^1_{j'})
\(S^b(t^1_j):={\displaystyle\text{ext}^b_{l\in \mathcal{L}}}~S^l(t^1_j)\,,\)
\(S^k(t^1_j):=\text{ext}^k_{l\in \mathcal{L}}~S^l(t^1_j)\,,\)
being \(\mathcal{L}\) the index for the set of assets and \(S^l(t^1_j)\) the price of the \(l\)-th asset at time \(t^1_j\) and \(\text{ext}\) can be either \(\max\) or \(\min\). For the default values (worst case scenario):
\(S^b(t^1_j):={\min_{l\in \mathcal{L}}}~S^l(t^1_j)\,,\)
\(S^k(t^1_j):=\max_{l\in \mathcal{L}}~S^l(t^1_j)\,.\)
Let us instantiate one of these objects.
[75]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"
]
cont_pay_ko = afs.KnockOutContingentPayment(underlying=equity["SX5E"],
obsdates=obs_dates, coupon_rate=0.04,
pay_barrier=2500, ko_barrier=5000,
min_time_pay=2,
calendar=calendars["Act365"],
min_time_redemption=0)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obsdates\(\rightarrow \{t^1_j\}_{j=0}^{j_T}\) ,coupon_rate\(\rightarrow c\),nominal\(\rightarrow N\),pay_barrier\(\rightarrow b_p\),ko_barrier\(\rightarrow b_k\),min_time_pay\(\rightarrow {j_0}\),calendar\(\rightarrow\) \(\tau(t,T)\),min_time_redemption\(\rightarrow j_1\,,\)pay_extremum_multi_asset\(\rightarrow \text{ext}^b\) (default value, “min”),ko_extremum_multi_asset\(\rightarrow \text{ext}^k\) (default value, “max”),credit_curve\(\rightarrow\) for discounts in Monte Carlo.
We can check the payoff
[76]:
# Random prices
prices = 5500 * np.random.random((1,7,1,1))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print("Payoff")
new = cont_pay_ko.payoff(prices = prices, dates_dic=dates_dic, n=0)
print(new)
Prices:
[[[[ 338.42967944]]
[[1933.15150016]]
[[1331.59640518]]
[[1718.17820554]]
[[ 901.3772033 ]]
[[3738.23180314]]
[[2622.75476664]]]]
Payoff
[[[0.]
[0.]
[0.]
[0.]
[0.]
[4.]
[0.]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section and the “Technical note (payoff method)” in the Asian options section.
The payoff function for a single asset is
Note that in this case we don’t have \(\delta_{j, j_T-n}\) since the payment can be at any observation date, not only at maturity.
For multiple assets
\(\diamondsuit\)
We can do the same for specific array of prices. For instance, knock-out condition activated before min_time, so the payoff is zero
[77]:
prices = np.array([8000, 2000, 2000, 2000, 2000, 5000, 2000]).reshape(1,7,1,1)
cont_pay_ko.payoff(prices = prices, dates_dic=dates_dic, n=0)
[77]:
array([[[0.],
[0.],
[0.],
[0.],
[0.],
[0.],
[0.]]])
Now, knock-out barrier crossed at min_time, so it pays the coupon:
[78]:
prices = np.array([2000, 2000, 8000, 2000, 2000, 5000, 2000]).reshape(1,7,1,1)
cont_pay_ko.payoff(prices = prices, dates_dic=dates_dic, n=0)
[78]:
array([[[0.],
[0.],
[4.],
[0.],
[0.],
[0.],
[0.]]])
Now min_time_red is one, so if the first price crosses the ko_barrier, this will not affect the remaining dates
[79]:
cont_pay_ko = afs.KnockOutContingentPayment(underlying=equity["SX5E"],
obsdates=obs_dates, coupon_rate=0.04,
pay_barrier=2500, ko_barrier=5000,
min_time_pay=2,
calendar=calendars["Act365"],
min_time_redemption=1)
prices = np.array([100000, 3000, 8000, 2000, 2000, 5000, 2000]).reshape(1,7,1,1)
cont_pay_ko.payoff(prices = prices, dates_dic=dates_dic, n=0)
[79]:
array([[[0.],
[0.],
[4.],
[0.],
[0.],
[0.],
[0.]]])
For multiple assets
[80]:
prices = 5350 * np.random.random([1, 7, 1, 2])
print(prices[:])
cont_pay_ko = afs.KnockOutContingentPayment(underlying=equity["SX5E"],
obsdates=obs_dates, coupon_rate=0.04,
pay_barrier=2500, ko_barrier=5000,
min_time_pay=0,
calendar=calendars["Act365"],
min_time_redemption=0,
pay_extremum_multi_asset="min",
ko_extremum_multi_asset="max")
cont_pay_ko.payoff(prices = prices, dates_dic=dates_dic, n=0)
[[[[1493.97090519 2553.79244109]]
[[5336.31194973 4783.78975359]]
[[4479.47828639 5264.10047493]]
[[ 604.61317091 2091.0335326 ]]
[[3474.31296346 3936.74382364]]
[[2825.56724338 4359.66735253]]
[[5270.57381342 5203.60229075]]]]
[80]:
array([[[0.],
[4.],
[0.],
[0.],
[0.],
[0.],
[0.]]])
Autocallable#
For a single asset
For each observation date \(\{t^1_j\}_{j=0}^{J}\) a bonus payment will be made if \(S(t^1_j)\ge b_p\) and the amount will be
where:
\(c\) is the coupon rate,
\(N\) the nominal,
\(S(t)\) is the price of the underlying at time \(t\),
\(C\), number of previously unpaid coupons.
For the nominal we can consider two cases.
First, an early redemption (possible for the observation dates \(\{t^1_j\}_{j=j_1}^{j_T}\)) will occur at 100% of the nominal, i.e. \(N\), if \(S(t^1_j)\ge b_e\). No more payment after an early redemption.
Second, if no early redemption occurred, the redemption amount at maturity will be determined as follows:
where
\(b_c\) is the capital protection barrier,
\(t_{j_T}=T\), maturity date,
\(S(t_{\text{initial}})\) is the initial level.
For multiple assets
Analogously, but, worst case scenario, i.e.,
\(S^p(t^1_j):=\min_{l\in \mathcal{L}}S^l(t^1_j)\) for the condition \(S(t^1_j)\ge b_p \,,\)
\(S^e(t^1_j):=\min_{l\in \mathcal{L}}S^l(t^1_j)\) for the condition \(S(t^1_j)\ge b_e \,,\)
\(S^c(t^1_j):=\min_{l\in \mathcal{L}}S^l(t^1_j)\) for the condition \(S(t^1_j)\ge b_c \,,\)
\(S^m(t^1_j):=\min_{l\in \mathcal{L}}S^l(t^1_j)\) for \({S^m(T)}/{S(t_{\text{initial}})}\), argument needed for the payoff of the nominal at maturity.
being \(\mathcal{L}\) the index for the set of assets and \(S^l(t^1_j)\) the price of the \(l\)-th asset at time \(t^1_j\).
Remark: now, it assumes that all the initial levels and barriers are the same for every underlying.
Let us instantiate one of these objects.
[83]:
initial_level = 100
coupon_rate= 0.05
nominal = 100
underlying = equity["SX5E"]
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"
]
prices = 105 * np.random.random((1,7,1,1))
early_barrier=initial_level
capital_barrier=0.54*initial_level
pay_barrier=0.54*initial_level
autocallable = afs.AutocallableComp(underlying=underlying, obsdates=obs_dates,
coupon_rate=coupon_rate,
initial_level=initial_level,
early_barrier=early_barrier,
capital_barrier=capital_barrier,
pay_barrier=pay_barrier,
calendar=calendars["Act365"], nominal=nominal,
min_time_redemption=0)
\(\text{Code}\) - math \((\TeX)\) translation dictionary
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obsdates\(\rightarrow \{t^1_j\}_{j=0}^{j_T}\) ,coupon_rate\(\rightarrow c\),nominal\(\rightarrow N\) (default=100),initial_level\(\rightarrow\) \(S(t_{\text{initial}})\),early_barrier\(\rightarrow b_e\),capital_barrier\(\rightarrow b_c\),pay_barrier\(\rightarrow b_p\),calendar\(\rightarrow\) \(\tau(t,T)\),min_time_redemption\(\rightarrow j_1\) (default = 0, i.e., early redemption is possible from the beginning),credit_curve\(\rightarrow\) for discounts in Monte Carlo.
We can check the payoff
[84]:
# Random prices
prices = 150 * np.random.random((1,7,1,2))
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
# print("Prices: \n", prices, "\n")
# print("Prices: \n", np.min(prices, axis=-1), "\n")
min_prices = np.min(prices, axis=-1)
min_price_series = pd.Series(data=min_prices.reshape(-1), index=dates_dic.index)
print(f'Payoff:\n{autocallable.payoff(prices, dates_dic, 0)}')
fig = px.line(min_price_series, x=min_price_series.index, y=min_price_series.values)
fig.add_trace(go.Scatter(x=[min_price_series.index[0], min_price_series.index[-1]], y=[early_barrier, early_barrier],
mode='lines', line=dict(color='red', width=2, dash='dot'), name='Early barrier'))
fig.add_trace(go.Scatter(x=[min_price_series.index[0], min_price_series.index[-1]], y=[capital_barrier, capital_barrier],
mode='lines', line=dict(color='green', width=2, dash='dot'), name='Capital barrier'))
fig.add_trace(go.Scatter(x=[min_price_series.index[0], min_price_series.index[-1]], y=[pay_barrier, pay_barrier],
mode='lines', line=dict(color='blue', width=2, dash='dot'), name='Pay barrier'))
fig.update_layout(xaxis_title='Dates', yaxis_title='Price')
fig.show()
Payoff:
[[[ 5.]
[105.]
[ 0.]
[ 0.]
[ 0.]
[ 0.]
[ 0.]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) Map assigning an index \(j\) to each date,n\(\rightarrow\) Number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
In this example, if
dates_dic.size > prices.shape[1]
the program returns an IndexError. This occurs because the number of simulation dates (\(|\mathcal{J}'|\)) should be at least equal to the observation dates (in this example dates_dic only contains the observation dates). Specifically, in the code we have:
indices = [dates_dic[date] for date in self.obsdates]
prices = prices[:, indices, :]
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section and the “Technical note (payoff method)” in the Asian options section.
The payoff of this product can be decomposed into simpler parts. Specifically, it can be written in terms of Knock-out contingent payments and a Barrier option.
Coupons. For each observation date \(\{t^1_j\}_{j=0}^{J}\) we define a Knock-out contingent payment with \(j_0 = j\) (at \(t_{j_0}^1\) the payment is activated) and coupon rate \(c\).
Nominal. For this we define three Knock-out contingent payments with early redemption barrier at \(b_e\) and a put up-and-out Barrier option:
\(K_1\): The payment barrier at \(b_e\), coupon rate equal to \(1\) with observation dates \(\{t^1_j\}_{j=0}^{J-1}\) and minimum payment date satisfying \(j_0 (K_1) = j_1\). It accounts for the early redemption at \(\{t^1_j\}_{j=j_1}^{J-1}\).
\(K_2\): The payment barrier at \(b_c\), coupon rate equal to \(1-c_p\) with observation dates \(\{t^1_j\}_{j=0}^{J}\). In this case \(j_0 (K_2) = j_T\).
\(K_3\): The payment barrier at \(0\), coupon rate equal to \(c_p\) with observation dates \(\{t^1_j\}_{j=0}^{J}\). In this case \(j_0 (K_3) = j_T\).
\(B^\text{put}\): The barrier \(B=b_e\), observation dates \(\{t^1_j\}_{j=j_1}^{J}\), \(K= b_c\), \(B=b_e\) and nominal \(N c_p\).
We have \(c_p := b_c/S(t_{\text{initial}})\). With this, the nominal is given by
For more details see AutocallableComp class in structured.py. Note also that there are other classes defined for this product which are not based on the decomposition in terms of simpler products. \(\diamondsuit\)
We can also use the plot_payoff method. Note that in this class the payoff depends on the value of \(S_t\) for several dates so we set it to zero except for \(S_T\).
[85]:
autocallable.plot_payoff(0, 100)
Butterfly#
The value of a butterfly option at maturity depends on strike price \(K_1\), \(K_2\) and \(K_3\) of the call \(\left( K_1 < K_2 < K_3 \right)\), and underlying price, \(S_T\) at that time. For a long position, this payoff (at maturity) is given by
where \(\text{kind}\in\{\text{call}, \text{put}\}\). Depending on the value of the underlying the payoff is given by:
and
with \(N\) being the nominal.
\(\textcolor{red}{Warning}\): It has been considered that \(N_i= N \cdot K_i\) \(\forall i \in \{1,2,3\}\). That means that the nominal has been taken according to its strike.
Let us instantiate one object of this kind:
[86]:
butterfly_call = afs.Butterfly(underlying=equity["SX5E"], maturity="20280418", low_strike=50, main_strike=75, cap_strike=100, kind='call',
calendar=calendars["Act360"], nominal=100)
butterfly_put = afs.Butterfly(underlying=equity["SX5E"], maturity="20280418", low_strike=50, main_strike=75, cap_strike=100, kind='put',
calendar=calendars["Act360"], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),low_strike\(\rightarrow K_1\),main_strike\(\rightarrow K_2\),cap_strike\(\rightarrow K_3\),kind\(\rightarrow\) \(\text{kind}\in\{\text{call}, \text{put}\}\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N_i\).
For the payoff, we can check it as follows:
[87]:
# Random prices
prices = 300 * np.random.random((1,7,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
call1 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=50, calendar=calendars["Act360"], nominal=100*50)
call2 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=75, calendar=calendars["Act360"], nominal=100*75)
call3 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100*100)
butterfly2_call= call1- 2*call2 + call3
print("Pieces payoff with calls", butterfly2_call.payoff(prices, dates_dic, 0))
print("Butterfly payoff with calls: \n", butterfly_call.payoff(prices, dates_dic, 0))
print('-'*75)
put1 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=50, calendar=calendars["Act360"], nominal=100*50)
put2 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=75, calendar=calendars["Act360"], nominal=100*75)
put3 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100*100)
butterfly2_put= put1- 2*put2 + put3
print("Pieces payoff with puts", butterfly2_put.payoff(prices, dates_dic, 0))
print("Butterfly payoff with puts: \n", butterfly_put.payoff(prices, dates_dic, 0))
Prices:
[[[[ 52.00735769 61.15069357]]
[[ 53.93394504 19.18465376]]
[[ 9.44545603 252.97375158]]
[[142.23663671 125.9669604 ]]
[[ 63.0069976 262.45267753]]
[[ 84.3357603 70.26990215]]
[[ 53.21430419 229.45177782]]]]
---------------------------------------------------------------------------
Pieces payoff with calls [[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[321.43041931]]]
Butterfly payoff with calls:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[321.43041931]]]
---------------------------------------------------------------------------
Pieces payoff with puts [[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[321.43041931]]]
Butterfly payoff with puts:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[321.43041931]]]
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
Since the butterfly option is defined with the method __new__, we must write butterfly.payoff(prices, dates_dic, 0)) instead of butterfly.payoff(prices=prices, dates_dic=dates_dic, 0)) to perform examples.
Financial note#
The strategy can help to mitigate the effects of price swings and changes in volatility. The strategy is designed to profit from a limited range of price movement, and it can be particularly effective when volatility is low.
[88]:
butterfly_call.plot_payoff(0,200)
[89]:
butterfly_put.plot_payoff(0,200)
Straddle#
The value of a straddle option at maturity depends on strike price \(K\) of the call and put and underlying price, \(S_T\) at that time. For a long position, this payoff (at maturity) is given by
with \(N\) being the nominal. Depending on the value of the underlying:
Let us instantiate one object of this kind:
[90]:
straddle = afs.Straddle(underlying=equity["SX5E"], maturity="20280418", strike=100,
calendar=calendars["Act360"], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),strike\(\rightarrow K\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[91]:
# Random prices
prices = 200 * np.random.random((1,7,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
call = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100)
put= afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100)
print("Pieces payoff", call.payoff(prices = prices, dates_dic = dates_dic, n=0) +
put.payoff(prices = prices, dates_dic = dates_dic, n=0))
print("Straddle payoff: \n", straddle.payoff(prices, dates_dic, 0))
Prices:
[[[[ 69.2096631 197.05715673]]
[[177.6251076 190.39986271]]
[[ 46.62399908 67.76040812]]
[[ 9.14089655 79.62316938]]
[[116.50064964 169.79468608]]
[[184.17411957 26.33974164]]
[[ 64.78333196 23.69185277]]]]
---------------------------------------------------------------------------
Pieces payoff [[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[35.21666804]]]
Straddle payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[35.21666804]]]
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
where \(f(S)\) is defined above.
Since the straddle option is defined with the method __new__, we must write straddle.payoff(prices, dates_dic, 0)) instead of straddle.payoff(prices=prices, dates_dic=dates_dic, 0)) to perform examples.
Financial note#
It profits from significant price movements in the underlying asset, regardless of whether the price moves up or down. The straddle strategy can be used in events that are likely to cause a large price movement.
[92]:
straddle.plot_payoff(0,200)
Strangle#
The value of a strangle option at maturity depends on strike price \(K_1\) of the put and \(K_2\) of the call \(\left( K_1 < K_2 \right)\), and underlying price, \(S_T\) at that time. For a long position, this payoff (at maturity) is given by
with \(N\) being the nominal. Depending on the value of the underlying:
Let us instantiate one object of this kind:
[93]:
strangle = afs.Strangle(underlying=equity["SX5E"], maturity="20280418", low_strike=50, cap_strike=100,
calendar=calendars["Act360"], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),low_strike\(\rightarrow K_1\),cap_strike\(\rightarrow K_2\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[94]:
# Random prices
prices = 200 * np.random.random((1,7,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
call = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100)
put= afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=50, calendar=calendars["Act360"], nominal=100)
print("Pieces payoff", call.payoff(prices = prices, dates_dic = dates_dic, n=0) +
put.payoff(prices = prices, dates_dic = dates_dic, n=0))
print("Strangle payoff: \n", strangle.payoff(prices, dates_dic, 0))
Prices:
[[[[102.88133002 170.54001273]]
[[158.63806126 1.99477262]]
[[ 27.0122678 146.70034208]]
[[180.28779502 193.58367159]]
[[ 74.69449798 21.36581268]]
[[104.64253818 4.15535479]]
[[ 26.24247024 150.58259583]]]]
---------------------------------------------------------------------------
Pieces payoff [[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[47.51505953]]]
Strangle payoff:
[[[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[ 0. ]
[47.51505953]]]
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
where \(f(S)\) is defined above.
Since the strangle option is defined with the method __new__, we must write strangle.payoff(prices, dates_dic, 0)) instead of strangle.payoff(prices=prices, dates_dic=dates_dic, 0)) to perform examples.
Financial note#
The strangle strategy is similar to the straddle strategy, in that it is used to profit from significant price movements in the underlying asset. However, it is used when the trader expects the underlying asset to experience a significant price movement, but is unsure of which direction the price will move.
[95]:
strangle.plot_payoff(0,200)
Condor#
The value of a condor option at maturity depends on strike price \(K_1\), \(K_2\), \(K_3\) and \(K_4\) of the call \(\left( K_1 < K_2 < K_3 < K_4 \right)\), and underlying price, \(S_T\) at that time. For a long position, this payoff (at maturity) is given by
\(\text{kind}\in\{\text{call}, \text{put}\}\). Depending on the value of the underlying the payoff :
and
with \(N\) being the nominal.
\(\textcolor{red}{Warning}\): It has been considered that \(N_i= N \cdot K_i\) \(\forall i \in \{1,2,3,4\}\). That means that the nominal has been taken according to its strike.
Let us instantiate one object of this kind:
[96]:
condor_call = afs.Condor(underlying=equity["SX5E"], maturity="20280418", low_strike=50, main_strike=70, high_strike=80, cap_strike=100, kind='call',
calendar=calendars["Act360"], nominal=100)
condor_put = afs.Condor(underlying=equity["SX5E"], maturity="20280418", low_strike=50, main_strike=70, high_strike=80, cap_strike=100, kind='put',
calendar=calendars["Act360"], nominal=100)
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),maturity\(\rightarrow T\),low_strike\(\rightarrow K_1\),main_strike\(\rightarrow K_2\),high_strike\(\rightarrow K_3\),cap_strike\(\rightarrow K_4\),kind\(\rightarrow\) \(\text{kind}\in\{\text{call}, \text{put}\}\),calendar\(\rightarrow\) \(\tau(t,T)\),nominal\(\rightarrow N\).
For the payoff, we can check it as follows:
[97]:
# Random prices
prices = 300 * np.random.random((1,7,1,2))
print("Prices: \n", prices)
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
call1 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=50, calendar=calendars["Act360"], nominal=100*50)
call2 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=70, calendar=calendars["Act360"], nominal=100*70)
call3 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=80, calendar=calendars["Act360"], nominal=100*80)
call4 = afs.Call(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100*100)
condor2_call= call1- call2 - call3 + call4
print("Pieces payoff with calls", condor2_call.payoff(prices, dates_dic, 0))
print("Condor payoff with calls: \n", condor_call.payoff(prices, dates_dic, 0))
print('-'*75)
put1 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=50, calendar=calendars["Act360"], nominal=100*50)
put2 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=70, calendar=calendars["Act360"], nominal=100*70)
put3 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=80, calendar=calendars["Act360"], nominal=100*80)
put4 = afs.Put(underlying=equity["SX5E"], maturity="20280418", strike=100, calendar=calendars["Act360"], nominal=100*100)
condor2_put= put1- put2 -put3 + put4
print("Pieces payoff with puts", condor2_put.payoff(prices, dates_dic, 0))
print("Condor payoff with puts: \n", condor_put.payoff(prices, dates_dic, 0))
Prices:
[[[[ 39.79284036 253.09747173]]
[[137.59172654 144.39810066]]
[[ 9.24583595 3.58147915]]
[[ 88.41018919 294.75066239]]
[[ 51.64665192 146.09641646]]
[[ 24.25600962 75.17156594]]
[[157.23325729 76.55465118]]]]
---------------------------------------------------------------------------
Pieces payoff with calls [[[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[-9.09494702e-13]]]
Condor payoff with calls:
[[[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[ 0.00000000e+00]
[-9.09494702e-13]]]
---------------------------------------------------------------------------
Pieces payoff with puts [[[0.]
[0.]
[0.]
[0.]
[0.]
[0.]
[0.]]]
Condor payoff with puts:
[[[0.]
[0.]
[0.]
[0.]
[0.]
[0.]
[0.]]]
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
Since the condor option is defined with the method __new__, we must write condor.payoff(prices, dates_dic, 0)) instead of condor.payoff(prices=prices, dates_dic=dates_dic, 0)) to perform examples.
Financial note#
The condor strategy aims to profit from the limited price movement of an underlying asset. When an investor implements a condor strategy, they are essentially betting that the underlying asset will stay within a certain price range until the expiration of the options contracts.
[98]:
condor_call.plot_payoff(0,200)
[99]:
condor_put.plot_payoff(0,200)
Product from function#
The product from function method is capable of instantiating an object \(P_T=f\left(\left(S(t^1_j)\right)_{j\in\mathcal{J}}\right)\), from its payoff at maturity.
Let us instantiate one example.
[100]:
def func(s):
return np.where(s < -100, s, np.where(s < 0, -s, np.where(s < 100, 1.3*s, 130)))
np.where()leverages the vectorization capability of NumPy, allowing for parallel computations on matrices or multidimensional arrays.
[101]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"]
product_from_function = afs.ProductFromFunction(underlying=equity["SX5E"], obsdates=obs_dates, func=func,calendar=calendars["Act365"])
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obsdates\(\rightarrow \{t^1_j\}_{j=0}^{J}\) observation dates. Note that the last observation date corresponds to maturity \(T\),func\(\rightarrow\) payoff function ,calendar\(\rightarrow\) \(\tau(t,T)\),pastmatters\(\rightarrow\) path dependance (pastmatters = Trueby default),func_asset\(\rightarrow\) \(\phi_\mathcal{L}\),nominal\(\rightarrow N\).
For the payoff, we can check it as follow:
[102]:
prices = 300 * np.random.random((1,7,1,1))
print("Prices: \n", prices, "\n")
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Product payoff: \n",product_from_function.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[ 27.38209911]]
[[104.91002533]]
[[ 94.09337668]]
[[238.41718411]]
[[189.28949774]]
[[279.41996426]]
[[111.05733932]]]]
---------------------------------------------------------------------------
Product payoff:
[[[ 0.]
[ 0.]
[ 0.]
[ 0.]
[ 0.]
[ 0.]
[130.]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) map assigning an index \(j\) to each date,n\(\rightarrow\) number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
where \(f(S)\) is defined above.
[103]:
product_from_function.plot_payoff(-200,200)
The function is capable the calculate the payoff of a product that’s \(\textbf{not path dependant}\).
Let us instantiate the call of an asian option.
[104]:
nominal=100
strike=50
def fpayoff_arith_as_put(s):
averages = np.mean(s, axis=1)
return (nominal/strike) * np.where(strike - averages > 0, strike - averages, 0)
[105]:
obs_dates = ["20220419",
"20230417",
"20240415",
"20250415",
"20260415",
"20270415",
"20280418"]
product_from_function = afs.ProductFromFunction(underlying=equity["SX5E"], obsdates=obs_dates, func=fpayoff_arith_as_put,calendar=calendars["Act365"])
\(\text{Code}\) - math \((\TeX)\) translation
underlying\(\rightarrow\) \((S_l(\cdot))_{l=0}^L\),obsdates\(\rightarrow \{t^1_j\}_{j=0}^{J}\) observation dates. Note that the last observation date corresponds to maturity \(T\),func\(\rightarrow\) payoff function ,calendar\(\rightarrow\) \(\tau(t,T)\),pastmatters\(\rightarrow\) past matters (pastmatters = Falseby default),func_asset\(\rightarrow\) \(\phi_\mathcal{L}\),nominal\(\rightarrow N\).
For the payoff, we can check it as follow:
[106]:
prices = 300 * np.random.random((1,7,1,1))
print("Prices: \n", prices, "\n")
dates_dic = pd.Series({pd.Timestamp('2022-04-19 00:00:00'): 0, pd.Timestamp('2023-04-17 00:00:00'): 1,
pd.Timestamp('2024-04-15 00:00:00'): 2, pd.Timestamp('2025-04-15 00:00:00'): 3,
pd.Timestamp('2026-04-15 00:00:00'): 4, pd.Timestamp('2027-04-15 00:00:00'): 5,
pd.Timestamp('2028-04-18 00:00:00'): 6})
print('-'*75)
print("Product payoff: \n",product_from_function.payoff(prices = prices, dates_dic=dates_dic, n=0))
Prices:
[[[[259.74020929]]
[[202.70499387]]
[[291.89341768]]
[[139.50188842]]
[[299.27293414]]
[[ 85.37490938]]
[[148.87172982]]]]
---------------------------------------------------------------------------
Product payoff:
[[[0.]
[0.]
[0.]
[0.]
[0.]
[0.]
[0.]]]
\(\text{Code}\) - math \((\TeX)\) translation
prices\(\rightarrow\) \(S\) (prices of the underlying(s)),dates_dic\(\rightarrow\) map assigning an index \(j\) to each date,n\(\rightarrow\) number of observation dates previous to a given observation date (needed for the Monte Carlo engine).
Technical note (payoff method)#
For the conventions and details on the price and payoff arrays see the “Technical note (payoff method)” in the Zero Coupon Bond section.
The payoff function of this product in this case is
where \(f(S)\) is defined above.